package br.org.bertol.mestrado.engine;

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CountDownLatch;

import org.apache.log4j.Logger;

import br.org.bertol.mestrado.Buscador;
import br.org.bertol.mestrado.engine.fitness.ClassInstantiation;
import br.org.bertol.mestrado.engine.optimisation.OrdenateSpacialPosition;
import br.org.bertol.mestrado.engine.optimisation.entity.Particle;
import br.org.bertol.mestrado.engine.optimisation.moo.OrdenatePareto;
import br.org.bertol.mestrado.engine.optimisation.pso.LinearInertiaReduction;
import br.org.bertol.mestrado.engine.optimisation.pso.ParticleSwarmOptimization;
import br.org.bertol.mestrado.engine.optimisation.pso.TypeMovement;

/**
 * @author contaqualquer
 */
public class EnginePso extends AbstractSearchEngine {

    /***/
    private final transient TypeMovement                    typeMovement;

    /***/
    private final transient List<ParticleSwarmOptimization> resultPSO;

    /***/
    private final transient float                           fatorInerciaMax;

    /***/
    private final transient float                           fatorInerciaMin;

    /***/
    private final transient float                           fatorCognitivo;

    /***/
    private final transient float                           fatorSocial;

    /***/
    private final transient int                             depth;

    /***/
    private final transient int                             repositorySize;

    /***/
    private final transient int                             populationSize;

    /**
     * @param startS
     * @param stopS
     * @param numEval
     * @param objectives
     * @param storeP
     * @param sid
     * @param typeMovement
     * @param initInertia
     * @param finalInertia
     * @param fatorInicialCognitivo
     * @param fatorInitSocial
     * @param depth
     * @param repositorySize
     * @param populationSize
     */
    public EnginePso(final CountDownLatch startS, final CountDownLatch stopS,
        final int numEval, final Objective[] objectives,
        final String storeP, final String sid,
        final TypeMovement typeMovement, final float initInertia,
        final float finalInertia, final float fatorInicialCognitivo,
        final float fatorInitSocial, final int depth,
        final int repositorySize, final int populationSize) {

        super(startS, stopS, numEval, objectives, storeP, sid);

        this.typeMovement = typeMovement;

        resultPSO = new ArrayList<ParticleSwarmOptimization>();

        this.fatorInerciaMax = (initInertia < 0 ? 0f : (initInertia > 1f ? 1f
                : initInertia));

        this.fatorInerciaMin = (finalInertia < 0 ? 0f : (finalInertia > 1f ? 1f
                : finalInertia));

        this.fatorCognitivo = (fatorInicialCognitivo < 0 ? fatorInicialCognitivo
                * -1
                : fatorInicialCognitivo);

        this.fatorSocial = (fatorInitSocial < 0 ? fatorInitSocial * -1
                : fatorInitSocial);

        this.depth = depth;

        this.repositorySize = repositorySize;

        this.populationSize = populationSize;

        setName(getName() + " | Tamanho da população " + this.populationSize
                + " | Fatores inerciais " + this.fatorInerciaMax + " -> "
                + this.fatorInerciaMin + " | Fatores cognitivos "
                + this.fatorCognitivo + " | Fatores social " + this.fatorSocial
                + " | Divisões do grid " + this.depth
                + " | Tamanho do reposit�rio " + this.repositorySize
                + " | Tipo movimento " + typeMovement.name());
    }

    @Override
    public final void run() {
        try {
            // deixa sistema ponto para entrar em ação
            startSystem.await();

            if (verify.isSystemEnable()) {
                // aramazena resultado do pso em arquivo
                PrintWriter outFile = new PrintWriter(new BufferedWriter(
                        new FileWriter(storePath + "/" + systemID
                        + "/resultado_avaliacao_" + systemID + "_PSO_"
                        + typeMovement.name() + "_" + typeEvaluation
                        + ".txt", true)));

                Logger.getLogger(this.getClass()).info("Start");

                outFile
                        .println("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");

                outFile.println(getName());

                Logger.getLogger(this.getClass())
                        .debug(
                               "Fatores: in�rcia [" + fatorInerciaMax + " : "
                        + fatorInerciaMin + "] | cognitivo => "
                        + fatorCognitivo + " | social => "
                        + fatorSocial + ". Aguarde.");

                final LinearInertiaReduction linearInertiaReduction = new LinearInertiaReduction(
                        fatorInerciaMax, fatorInerciaMin, numIteracoes);

                final List<Particle> swarm = Buscador.randomSolution
                        .generateRandomPSOSolutions(
                                                    populationSize,
                                                    linearInertiaReduction,
                                                    fatorCognitivo,
                                                    fatorSocial,
                                                    new ClassInstantiation(
                        verify
                        .getStrongConstraint()));

                // ordena a lista de part�culas
                Collections
                        .sort(swarm, new OrdenateSpacialPosition());

                final ParticleSwarmOptimization particleSwarmOptimization = new ParticleSwarmOptimization(
                        verify, swarm, typeMovement, numIteracoes, depth,
                        repositorySize);

                final long iniTime = System.currentTimeMillis();

                particleSwarmOptimization.evaluate();

                resultPSO.add(particleSwarmOptimization);

                // ordena a lista de part�culas
                Collections.sort(particleSwarmOptimization.getSwarm(),
                                 new OrdenatePareto<Particle>());

                Collections.sort(particleSwarmOptimization.getSwarm(),
                                 new OrdenateSpacialPosition());

                outFile.println("Fatores: in�rcia [" + fatorInerciaMax + " : "
                        + fatorInerciaMin + "] | cognitivo => "
                        + fatorCognitivo + " | social => " + fatorSocial + ".");

                outFile.println("Sistema " + systemID
                        + " ParticleSwarmOptimization  - Resultado final ");

                if (repositorySize > 1) {
                    outFile.println("Fronteira de Pareto Encontrada");
                }

                final List<Particle> paretoFront = particleSwarmOptimization
                        .getParetoBorder();

                Collections.sort(paretoFront, new OrdenatePareto<Particle>());

                Collections.sort(paretoFront,
                                 new OrdenateSpacialPosition());

                for (Particle particle : paretoFront) {
                    outFile.println("| " + particle.toString() + " |");
                }

                outFile
                        .println("Processamento encerrado. Tempo total: "
                                + (float) ((System.currentTimeMillis() - iniTime) / 1000)
                                + " segundos.");

                outFile.flush();

                outFile.close();

                Logger.getLogger(this.getClass()).info("End");
            }
        } catch (IOException e) {
            Logger.getLogger(this.getClass()).error(
                                                    "Erro ao executar sistema "
                    + systemID, e);
        } catch (InterruptedException e) {
            Logger.getLogger(this.getClass()).error(
                                                    "Erro ao executar sistema "
                    + systemID, e);
        } catch (Exception e) {
            Logger.getLogger(this.getClass()).error(
                                                    "Erro ao executar sistema "
                    + systemID, e);
        }
        // notifica main thread
        stopSystem.countDown();
    }

    /**
     * @return the typeMovement
     */
    public final TypeMovement getTypeMovement() {
        return typeMovement;
    }

    /**
     * @return the resultPSO
     */
    public final List<ParticleSwarmOptimization> getResultPSO() {
        return resultPSO;
    }

    @Override
    public final void writeToFile(final PrintWriter outFile) {

        outFile.println(getName());

        for (ParticleSwarmOptimization particleSwarmOptimization : resultPSO) {

            if (verify.getObjectives().length == 1) {

                final Iterator<Particle> iterator = particleSwarmOptimization
                        .getSwarm().iterator();

                outFile.println(iterator.next().toString());
            } else {

                for (final Particle particle : particleSwarmOptimization
                        .getParetoBorder()) {
                    outFile.println(particle.toString());
                }
            }
        }

    }
}
